/* Zatok - язык программирования текстовых игр
 * (С) 2010 - 2012 Меркулов Матвей Юрьевич
 * Код лицензирован под лицензией GNU LGPL
 * Вы можете прочитать текст лицензии по адресу:
 * http://www.gnu.org/copyleft/lesser.html
 * или прочитать русский перевод по адресу:
 * http://ru.wikisource.org/wiki/GNU_Lesser_General_Public_License
 */

package parser;

import java.util.LinkedList;
import java.util.ListIterator;

public class CReplaceExp {
	private static LinkedList<CReplaceExp> ReplaceExps = new LinkedList<CReplaceExp>();
	private LinkedList<String> SearchFor;
	private LinkedList<String> ReplaceTo;
	private boolean FixedBeginning = false;
	private boolean FixedEnding = false;



	public static void Create( String SearchForSring, String ReplaceToSring ) {
		CReplaceExp ReplaceExp = new CReplaceExp();
		ReplaceExp.SearchFor = CParser.ToTokenList( SearchForSring );
		ReplaceExp.ReplaceTo = CParser.ToTokenList( ReplaceToSring );

		// установка флагов фиксированного начала и конца шаблона
		// фиксированное начало означает, что шаблон не будет смещаться вперед при проверке
		// фиксированный конец означает, что шаблон должен совпасть с концом исходного списка токенов
		// эти флаги задаются вертикальными чертами в начале или конце шаблона поиска
		if ( ReplaceExp.SearchFor.getFirst().equals( "$" ) ) {
			ReplaceExp.SearchFor.removeFirst();
			ReplaceExp.FixedBeginning = true;
		}
		if ( ReplaceExp.SearchFor.getLast().equals( "$" ) ) {
			ReplaceExp.SearchFor.removeLast();
			ReplaceExp.FixedEnding = true;
		}

		ReplaceExps.add( ReplaceExp );
	}



	public static LinkedList<String> SearchAndReplace( LinkedList<String> Tokens, boolean Part ) {
		// заполнение массива выражений для токенов типа %N
		LinkedList<LinkedList<String>> TokenData = new LinkedList<LinkedList<String>>();
		for ( int N = 0; N < 10; N++ ) {
			TokenData.add( new LinkedList<String>() );
		}

		for ( CReplaceExp ReplaceExp : ReplaceExps ) {
			// пропускаем модуль замены, если он имеет фиксированное начало или конец, а рассматриваемое выражение является частичным
			if ( Part && ( ReplaceExp.FixedBeginning || ReplaceExp.FixedEnding ) ) continue;

			// цикл смещения от начала исходного списка токенов для накладывания на него шаблона поиска-замены
			for ( int StartOfSearch = 0; StartOfSearch <= Tokens.size() - ReplaceExp.SearchFor.size(); StartOfSearch++ ) {
				ListIterator<String> SearchForIterator = ReplaceExp.SearchFor.listIterator(); // перечислитель для шаблона поиска-замены
				ListIterator<String> TokensIterator = Tokens.listIterator( StartOfSearch ); // перечислитель для исходного списка токенов
				Search: while ( true ) {
					if ( !SearchForIterator.hasNext() ) {
						// если шаблон с фиксированным окончанием и после полного совпадения еще остались токены - совпадения нет
						if ( ReplaceExp.FixedEnding ) {
							if ( TokensIterator.hasNext() ) break;
						}
						
						// Замена исходного списка токенов на новый с преобразованием выражений поиска-замены
						LinkedList<String> NewTokens = new LinkedList<String>();

						// Добавление в новый список токенов начальных элементов исходного списка перед совпавшим куском
						ListIterator<String> TokensIterator2 = Tokens.listIterator();
						for ( int N = 0; N < StartOfSearch; N++ ) NewTokens.add( TokensIterator2.next() );

						for ( String ReplaceToken : ReplaceExp.ReplaceTo ) {
							if ( ReplaceToken.startsWith( "%" ) ) {
								
								// Добавление содержимого выражения с указанным номером в результат
								int TokenNum = Integer.parseInt( ReplaceToken.substring( 1 ) );
								LinkedList<String> ThisTokenData = TokenData.get( TokenNum );
								for ( String Token : ThisTokenData ) {
									NewTokens.add( Token );
								}
							} else {
								// Добавление простого токена
								NewTokens.add( ReplaceToken );
							}
						}

						// Добавление остатка исходного списка токенов после совпадения всего шаблона в результат
						while( TokensIterator.hasNext() ) NewTokens.add( TokensIterator.next() );

						//CParser.PrintTokens( NewTokens );
						return SearchAndReplace( NewTokens, Part );
					} else if ( !TokensIterator.hasNext() ) {
						break;
					}

					String ReplaceExpToken = SearchForIterator.next();
					String TokensToken = TokensIterator.next();

					if ( ReplaceExpToken.startsWith( "%" ) ) {
						// разбор выражений - токенов типа %N
						boolean AllowSquareBrackets = true;
						boolean DisallowUnnamedBrackets = false;
						if( ReplaceExpToken.endsWith( "0" ) ) AllowSquareBrackets = false;
						if( ReplaceExpToken.endsWith( "9" ) ) DisallowUnnamedBrackets = true;
						int TokenNum = Integer.parseInt( ReplaceExpToken.substring( 1, 2 ) );
						LinkedList<String> NewTokens = new LinkedList<String>();

						if( TokensToken.equals( "(" ) ) {
							// обработка выражения в скобках
							if( DisallowUnnamedBrackets ) break;
							TokensIterator.previous();
						} else if ( CParser.IsSeparator( TokensToken ) ) {
							// выход, если вместо объекта / названия функции встречен разделитель
							break;
						} else {
							NewTokens.add( TokensToken );
						}

						if ( TokensIterator.hasNext() ) {
							TokensToken = TokensIterator.next();
							if ( TokensToken.equals( "(" ) || ( AllowSquareBrackets && TokensToken.equals( "[" ) ) ) {
								// если следующий токен - скобка, тогда заносим в список выражения скобки и все данные в скобках с учетом вложенных скобок
								int BracketsQuantity = 0;
								while (true) {
									NewTokens.add( TokensToken );
									if ( TokensToken.equals( "(" ) || ( ( AllowSquareBrackets || BracketsQuantity > 0 ) && TokensToken.equals( "[" ) ) ) {
										BracketsQuantity++;
									} else if ( TokensToken.equals( ")" ) || ( ( AllowSquareBrackets || BracketsQuantity > 1 ) && TokensToken.equals( "]" ) ) ) {
										BracketsQuantity--;
									}
									if ( BracketsQuantity == 0 ) break;
									if ( !TokensIterator.hasNext() ) CParser.Error();
									TokensToken = TokensIterator.next();
								}
							} else {
								// иначе возвращаем итератор назад на одну позицию
								TokensIterator.previous();
							}
						}
						// установка выражения в списке под указанным номером
						TokenData.set( TokenNum, NewTokens );
					} else {
						// если обычные токены не совпадают - все выражение не совпало, переходим к следующему
						if( !ReplaceExpToken.equals( TokensToken ) ) break;
					}
				}
				if ( ReplaceExp.FixedBeginning ) break;
			}
		}

		return Tokens;
	}
}
